/*
 * Copyright 2010, Andrew M Gibson
 *
 * www.andygibson.net
 *
 * This file is part of DataValve.
 *
 * DataValve is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * DataValve is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with DataValve.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.fluttercode.datavalve.provider;

import java.util.List;

import org.fluttercode.datavalve.Paginator;
import org.fluttercode.datavalve.provider.util.DataQuery;

/**
 * Base class for any data provider that is driven by a Query Language. This
 * class implements the fundamentals of generating a {@link DataQuery} object
 * that can then be used to query data in the different query language providers
 * (Hql, Ejbql, Sql etc).
 *
 * By using an internal query to hold the statement and the parameters, we can
 * utilize that in different ways in different sub classes for different data
 * access mechanisms
 *
 * @author Andy Gibson
 */
public abstract class AbstractQLDataProvider<T> extends AbstractParameterizedDataProvider<T> {

    private static final long serialVersionUID = 1L;

    /** 查询语句 */
    private String selectStatement;
    /** 数量语句 */
    private String countStatement;
    /** SQL后缀 */
    private String suffix = null;

    public String getSelectStatement() {
        return selectStatement;
    }

    public void setSelectStatement(String selectStatement) {
        this.selectStatement = selectStatement;
    }

    public String getCountStatement() {
        return countStatement;
    }

    public void setCountStatement(String countStatement) {
        this.countStatement = countStatement;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }

    @Override
    protected Integer doFetchResultCount() {
        DataQuery query = buildDataQuery(getCountStatement(), false, null);
        return queryForCount(query);
    }

    @Override
    protected List<T> doFetchResults(Paginator paginator) {
        // fetch the results by building the data query and handing it off to a
        // query execution method.
        DataQuery query = buildDataQuery(getSelectStatement(), true, paginator);

        Integer count = paginator.includeAllResults() ? null : paginator.getMaxRows() + 1;
        List<T> temp = queryForResults(query, paginator.getFirstResult(), count);

        // if we returned more than maxRows, then we have more data to fetch
        boolean nextAvailable = (!paginator.includeAllResults()) && temp.size() > paginator.getMaxRows();
        paginator.setNextAvailable(nextAvailable);
        if (paginator.includeAllResults() || temp.size() < paginator.getMaxRows()) {
            return temp;
        }

        // create a sublist containing maxRows number of rows.
        return temp.subList(0, paginator.getMaxRows());
    }

    /**
     * Go fetch the results from the database using the defined query
     * and first result and result size information.
     *
     * Override in subclasses to provide different implementations for fetching
     * the results
     *
     * @param query The {@link DataQuery} instance containing the statement and parameters
     * @param firstResult indicates the first row that is to be returned or null if we are fetching from the first row.
     * @param count the number of rows to return or null if we are to return them all.
     * @return List of data
     */
    protected abstract List<T> queryForResults(DataQuery query, Integer firstResult, Integer count);

    /**
     * Query for the actual result count value using the information
     * defined in the {@link DataQuery} reference passed in.
     * In most cases, the statement needs executing and the count value
     * retrieved from the query.
     *
     * Override in subclasses to provide different implementations for fetching
     * the count
     *
     * @param query
     * @return
     */
    protected abstract Integer queryForCount(DataQuery query);

    /**
     * Builds the {@link DataQuery} object from the base query
     * statement and the provider information.
     * Pass the {@link Paginator} for order information as well as a flag
     * indicating whether ordering is included. We don't want ordering on count
     * statements so make sure it is false for those.
     *
     * This method is all about taking the provider statements and restrictions
     * and assembling the final query including parameterizing the restrictions
     * and adding the order by clause if needed.
     *
     * Override in subclasses to provide different implementations for
     * generating the query, althought a default implementation is provided in
     * the {@link AbstractQueryDataProvider} which most Query Language based
     * providers should subclass.
     *
     * In most final subclasses for JPA, SQL, Hibernate etc, you should just
     * need to implement the <code>queryForCount</code> and
     * <code>queryForResults</code> methods since these are specific to the
     * actual data connectivity mechanism whereas more general code has been
     * pulled up into parent classes to be inherited.
     *
     * @param baseStatement Initial statement to use for selecting data
     * @param includeOrdering indicates whether the order clause should be added to the final statement
     * @param paginator Paginator for determining the order clause.
     * @return {@link DataQuery} reference that contains the final QL and parameters for obtaining the data.
     */
    protected abstract DataQuery buildDataQuery(String baseStatement, boolean includeOrdering, Paginator paginator);
}
